/**
 * 
 */
package org.mspring.mlog.web.security.login;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * 登录失败后跳转方法
 * 
 * @author Gao Youbo
 * @since 2013-1-14
 */
public class LoginAuthenticationFailureHandler implements AuthenticationFailureHandler {

    private static final Logger log = Logger.getLogger(LoginAuthenticationFailureHandler.class);

    private String defaultFailureUrl; // 默认登录失败跳转链接
    private String failureUrlParameter = null; // 页面定义的登录失败跳转链接参数名
    private boolean useReferer = false; // 是否从Referer中获取登录失败跳转链接
    private boolean alwaysUseDefaultFailureUrl = false; // 是否始终使用默认登录失败链接
    private boolean forwardToDestination = false;
    private boolean allowSessionCreation = true;
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    public LoginAuthenticationFailureHandler() {
    }

    public LoginAuthenticationFailureHandler(String defaultFailureUrl) {
        setDefaultFailureUrl(defaultFailureUrl);
    }

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        // TODO Auto-generated method stub
        String failureUrl = determineTargetUrl(request, response);
        if (failureUrl == null) {
            log.debug("No failure URL set, sending 401 Unauthorized error");
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication Failed: " + exception.getMessage());
        } else {
            saveException(request, exception);

            if (forwardToDestination) {
                log.debug("Forwarding to " + failureUrl);
                request.getRequestDispatcher(failureUrl).forward(request, response);
            } else {
                log.debug("Redirecting to " + failureUrl);
                redirectStrategy.sendRedirect(request, response, failureUrl);
            }
        }
    }

    /**
     * 登录失败跳转链接
     * 
     * @param request
     * @param response
     * @return
     */
    protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
        if (isAlwaysUseDefaultFailureUrl()) {
            return defaultFailureUrl;
        }

        // Check for the parameter and use that if available
        String failureUrl = null;
        if (failureUrlParameter != null) {
            failureUrl = request.getParameter(failureUrlParameter);
            if (StringUtils.hasText(failureUrl)) {
                log.debug("Found targetUrlParameter in request: " + failureUrl);
                return failureUrl;
            }
        }

        if (useReferer && !StringUtils.hasLength(failureUrl)) {
            failureUrl = request.getHeader("Referer");
            log.debug("Using Referer header: " + failureUrl);
        }

        if (!StringUtils.hasText(failureUrl)) {
            failureUrl = defaultFailureUrl;
            log.debug("Using default Url: " + failureUrl);
        }
        return failureUrl;
    }

    /**
     * Caches the {@code AuthenticationException} for use in view rendering.
     * <p>
     * If {@code forwardToDestination} is set to true, request scope will be
     * used, otherwise it will attempt to store the exception in the session. If
     * there is no session and {@code allowSessionCreation} is {@code true} a
     * session will be created. Otherwise the exception will not be stored.
     */
    protected final void saveException(HttpServletRequest request, AuthenticationException exception) {
        if (forwardToDestination) {
            request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
        } else {
            HttpSession session = request.getSession(false);

            if (session != null || allowSessionCreation) {
                request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
            }
        }
    }

    public void setDefaultFailureUrl(String defaultFailureUrl) {
        Assert.isTrue(UrlUtils.isValidRedirectUrl(defaultFailureUrl), "'" + defaultFailureUrl + "' is not a valid redirect URL");
        this.defaultFailureUrl = defaultFailureUrl;
    }

    protected boolean isUseForward() {
        return forwardToDestination;
    }

    public void setUseForward(boolean forwardToDestination) {
        this.forwardToDestination = forwardToDestination;
    }

    public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
        this.redirectStrategy = redirectStrategy;
    }

    protected RedirectStrategy getRedirectStrategy() {
        return redirectStrategy;
    }

    protected boolean isAllowSessionCreation() {
        return allowSessionCreation;
    }

    public void setAllowSessionCreation(boolean allowSessionCreation) {
        this.allowSessionCreation = allowSessionCreation;
    }

    public String getFailureUrlParameter() {
        return failureUrlParameter;
    }

    public void setFailureUrlParameter(String failureUrlParameter) {
        this.failureUrlParameter = failureUrlParameter;
    }

    public boolean isAlwaysUseDefaultFailureUrl() {
        return alwaysUseDefaultFailureUrl;
    }

    public void setAlwaysUseDefaultFailureUrl(boolean alwaysUseDefaultFailureUrl) {
        this.alwaysUseDefaultFailureUrl = alwaysUseDefaultFailureUrl;
    }

    public void setUseReferer(boolean useReferer) {
        this.useReferer = useReferer;
    }

}
